package com.dk.hi.gui;

import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
import org.newdawn.slick.ImageBuffer;
import org.newdawn.slick.SlickException;

/**
 * Special image handling border.
 */
public class BorderedImage extends Image {
	/**
	 * The border size
	 */
	private Integer borderSize;

	/**
	 * The border color
	 */
	private Color borderColor;

	/**
	 * The cached image representing the border
	 */
	private Image borderCache;

	/**
	 * Constructor
	 * 
	 * @param path
	 *            - image path
	 * @throws SlickException
	 */
	public BorderedImage(String path) throws SlickException {
		super(path);
	}

	/**
	 * Constructor
	 * 
	 * @param source
	 *            - image source
	 * @throws SlickException
	 */
	public BorderedImage(Image source) throws SlickException {
		super(source);
	}

	/**
	 * Sets the border size
	 * 
	 * @param borderSize
	 */
	public void setBorderSize(Integer borderSize) {
		this.borderSize = borderSize;
	}

	/**
	 * Sets the border color
	 * 
	 * @param borderColor
	 */
	public void setBorderColor(Color borderColor) {
		this.borderColor = borderColor;
	}

	/**
	 * @inheridDoc
	 */
	public void draw(float x, float y, float width, float height, Color filter) {
		// The border image is generated only once and only if border size and
		// border color are defined
		if (borderSize != null && borderSize != 0 && borderCache == null && borderColor != null) {
			// There is a border to render
			final int imageWidth = getWidth();
			final int imageHeight = getHeight();

			// Here is a matrix of each pixel's Color in the Image. It is bigger
			// than the original image in order to create the border.
			// This will create a matrix of the image's pixels colors with empty
			// cells around to make the border
			Color[][] matrix = new Color[imageWidth + 2 * borderSize][imageHeight + 2 * borderSize];
			for (int mx = 0; mx < imageWidth; mx++) {
				for (int my = 0; my < imageHeight; my++) {
					matrix[mx + borderSize][my + borderSize] = getColor(mx, my);
				}
			}

			// The border data will we wrtitten in this buffer
			ImageBuffer data = new ImageBuffer(imageWidth + 2 * borderSize, imageHeight + 2 * borderSize);

			// The colors of the wanted border
			final int red = borderColor.getRed();
			final int green = borderColor.getGreen();
			final int blue = borderColor.getBlue();

			// Processing cells
			for (int mx = 0; mx < imageWidth + 2 * borderSize; mx++) {
				for (int my = 0; my < imageHeight + 2 * borderSize; my++) {
					// If a cell around this one is colored, it has to be
					// colored for border
					if (isBorderCell(matrix, mx, my)) {
						data.setRGBA(mx, my, red, green, blue, 255);
					}
				}
			}

			// The buffer is filled, the image can be created
			borderCache = new Image(data);
		}
		if (borderCache != null) {
			// A border is created, let's render it!
			borderCache.draw(x - borderSize, y - borderSize);
		}
		super.draw(x, y, width, height, filter);
	}

	/**
	 * Tests if a cell is to be colored for border rendering
	 * 
	 * @param matrix
	 *            - the image matrix
	 * @param x
	 *            - the x coordinate int the matrix
	 * @param y
	 *            - the y coordinate in the matrix
	 * @return true if the cell is to be colored for border rendering
	 */
	private boolean isBorderCell(Color[][] matrix, Integer x, Integer y) {
		// First, if the current cell is colored, there is no need of processing
		// it
		if (cellIsColored(matrix[x][y])) {
			return false;
		}

		final int matrixWidth = matrix.length;
		final int matrixHeight = matrix[0].length;

		for (int dx = -borderSize; dx <= borderSize; dx++) {
			if (x + dx < 0 || x + dx >= matrixWidth) {
				// Out of the matrix
				continue;
			}
			for (int dy = -borderSize; dy <= borderSize; dy++) {
				if (y + dy < 0 || y + dy >= matrixHeight) {
					// Out of the matrix
					continue;
				}
				if (cellIsColored(matrix[x + dx][y + dy])) {
					// There is one!
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * @param color
	 *            - the color to test
	 * @return true if the color is not transparent (alpa = 0)
	 */
	private boolean cellIsColored(Color color) {
		if (color == null || (color != null && color.a == 0f)) {
			return false;
		}
		return true;
	}
}
